# apps/exam_papers/tests.py
"""
试卷模块测试套件
包含：模型、序列化器、视图集（含权限与自定义动作）测试
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from decimal import Decimal
from unittest.mock import patch
# 新增序列化器导入
from .serializers import (
    ExamPaperListSerializer,
    ExamPaperDetailSerializer,
    ExamPaperCreateUpdateSerializer
)

from .models import ExamPaper
from apps.subjects.models import Subject

User = get_user_model()


class ExamPaperModelTest(TestCase):
    """测试 ExamPaper 模型行为"""

    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='pass12345'
        )
        self.subject = Subject.objects.create(
            name='数学',
            code='MATH101',
        )

    def test_create_exam_paper(self):
        """测试创建试卷"""
        paper = ExamPaper.objects.create(
            title='高一月考数学卷',
            total_score=Decimal('100.00'),
            duration_minutes=90,
            difficulty=3,
            creator=self.user,
            subject=self.subject,
            status='draft'
        )
        self.assertEqual(paper.title, '高一月考数学卷')
        self.assertEqual(paper.status, 'draft')
        self.assertEqual(paper.creator, self.user)
        self.assertEqual(paper.subject, self.subject)
        self.assertTrue(paper.created_at is not None)
        self.assertTrue(paper.updated_at is not None)

    def test_default_values(self):
        """测试字段默认值"""
        paper = ExamPaper.objects.create(
            title='默认试卷',
            creator=self.user,
            subject=self.subject
        )
        self.assertEqual(paper.total_score, Decimal('100.00'))
        self.assertEqual(paper.duration_minutes, 90)
        self.assertEqual(paper.difficulty, 3)
        self.assertEqual(paper.status, 'draft')

    def test_status_display(self):
        """测试状态显示名称"""
        paper = ExamPaper.objects.create(
            title='状态测试卷',
            creator=self.user,
            subject=self.subject,
            status='published'
        )
        self.assertEqual(paper.get_status_display(), '已发布')

    def test_string_representation(self):
        """测试 __str__ 方法"""
        paper = ExamPaper.objects.create(
            title='字符串测试卷',
            creator=self.user,
            subject=self.subject
        )
        self.assertEqual(str(paper), '字符串测试卷')


class ExamPaperSerializerTest(TestCase):
    """测试序列化器逻辑"""

    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='pass12345'
        )
        self.subject = Subject.objects.create(
            name='物理',
            code='PHYS201',
        )

    def test_list_serializer_output(self):
        """测试列表序列化器输出字段"""
        paper = ExamPaper.objects.create(
            title='列表测试卷',
            total_score=Decimal('120.00'),
            duration_minutes=120,
            difficulty=4,
            creator=self.user,
            subject=self.subject,
            status='published'
        )
        serializer = ExamPaperListSerializer(paper)
        data = serializer.data
        self.assertEqual(set(data.keys()), {
            'id', 'title', 'total_score', 'duration_minutes',
            'difficulty', 'creator', 'subject', 'status',
            'status_display', 'created_at', 'updated_at'
        })
        self.assertEqual(data['creator'], 'testuser')
        self.assertEqual(data['subject'], '物理')

    def test_detail_serializer_includes_all_fields(self):
        """测试详情序列化器包含所有字段"""
        paper = ExamPaper.objects.create(
            title='详情测试卷',
            description='这是一份详细的试卷',
            total_score=Decimal('150.00'),
            duration_minutes=150,
            difficulty=5,
            creator=self.user,
            subject=self.subject,
            status='draft',
            generated_from={'topics': ['algebra', 'geometry']}
        )
        serializer = ExamPaperDetailSerializer(paper)
        data = serializer.data
        self.assertIn('description', data)
        self.assertIn('generated_from', data)
        # 假设 UserSimpleSerializer 返回 username
        self.assertEqual(data['creator']['username'], 'testuser')
        self.assertEqual(data['subject']['name'], '物理')

    def test_create_serializer_validation(self):
        """测试创建序列化器验证逻辑"""
        # 缺少必填字段
        data = {'title': '', 'subject': self.subject.id}
        serializer = ExamPaperCreateUpdateSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('title', serializer.errors)

        # 难度不在范围内
        data = {
            'title': '验证测试卷',
            'subject': self.subject.id,
            'difficulty': 6
        }
        serializer = ExamPaperCreateUpdateSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('difficulty', serializer.errors)

        # 正确数据
        data = {
            'title': '有效试卷',
            'subject': self.subject.id,
            'difficulty': 3
        }
        serializer = ExamPaperCreateUpdateSerializer(data=data)
        self.assertTrue(serializer.is_valid())

    def test_update_serializer_partial_update(self):
        """测试更新序列化器支持部分更新"""
        paper = ExamPaper.objects.create(
            title='原试卷',
            creator=self.user,
            subject=self.subject
        )
        data = {'title': '更新后的标题'}
        serializer = ExamPaperCreateUpdateSerializer(
            instance=paper,
            data=data,
            partial=True
        )
        self.assertTrue(serializer.is_valid())
        updated_paper = serializer.save()
        self.assertEqual(updated_paper.title, '更新后的标题')


class ExamPaperViewSetTest(APITestCase):
    """测试 ExamPaperViewSet 的 API 行为"""

    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='pass12345'
        )
        self.other_user = User.objects.create_user(
            username='otheruser',
            email='other@example.com',
            password='pass12345'
        )
        self.subject = Subject.objects.create(
            name='化学',
            code='CHEM301',
        )
        self.admin_user = User.objects.create_superuser(
            username='admin',
            email='admin@example.com',
            password='admin123'
        )

        # 创建用户自己的试卷
        self.own_paper = ExamPaper.objects.create(
            title='我的试卷',
            creator=self.user,
            subject=self.subject,
            status='draft'
        )

        # 创建他人试卷
        self.other_paper = ExamPaper.objects.create(
            title='他人的试卷',
            creator=self.other_user,
            subject=self.subject,
            status='draft'
        )

        # URL 别名（需确保在 urls.py 中 basename='exam-paper'）
        self.list_url = reverse('exam-paper-list')
        self.detail_url = reverse('exam-paper-detail', kwargs={'pk': self.own_paper.pk})
        self.other_detail_url = reverse('exam-paper-detail', kwargs={'pk': self.other_paper.pk})
        self.publish_url = reverse('exam-paper-publish', kwargs={'pk': self.own_paper.pk})
        self.archive_url = reverse('exam-paper-archive', kwargs={'pk': self.own_paper.pk})
        self.public_url = reverse('exam-paper-public')
        self.stats_url = reverse('exam-paper-stats', kwargs={'pk': self.own_paper.pk})

    def test_list_requires_authentication(self):
        """未登录用户不能访问列表"""
        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_user_can_list_own_papers(self):
        """用户只能看到自己的试卷"""
        self.client.force_authenticate(user=self.user)
        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['id'], self.own_paper.pk)

    def test_admin_can_list_all_papers(self):
        """管理员可查看所有试卷"""
        self.client.force_authenticate(user=self.admin_user)
        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 2)

    def test_create_exam_paper(self):
        """测试创建试卷"""
        self.client.force_authenticate(user=self.user)
        data = {
            'title': '新创建的试卷',
            'subject': self.subject.pk,
            'difficulty': 4
        }
        response = self.client.post(self.list_url, data)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['title'], '新创建的试卷')
        self.assertEqual(response.data['creator'], self.user.pk)

    def test_update_own_paper(self):
        """用户可以更新自己的试卷"""
        self.client.force_authenticate(user=self.user)
        data = {'title': '更新标题'}
        response = self.client.patch(self.detail_url, data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.own_paper.refresh_from_db()
        self.assertEqual(self.own_paper.title, '更新标题')

    def test_update_other_paper_denied(self):
        """用户不能更新他人试卷"""
        self.client.force_authenticate(user=self.user)
        data = {'title': '篡改标题'}
        response = self.client.patch(self.other_detail_url, data)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_publish_paper(self):
        """测试发布试卷"""
        self.client.force_authenticate(user=self.user)
        response = self.client.post(self.publish_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], '已发布')
        self.own_paper.refresh_from_db()
        self.assertEqual(self.own_paper.status, 'published')

    def test_publish_already_published(self):
        """不能重复发布"""
        self.own_paper.status = 'published'
        self.own_paper.save()
        self.client.force_authenticate(user=self.user)
        response = self.client.post(self.publish_url)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('已发布', response.data['detail'])

    def test_archive_paper(self):
        """测试归档试卷"""
        self.client.force_authenticate(user=self.user)
        response = self.client.post(self.archive_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.own_paper.refresh_from_db()
        self.assertEqual(self.own_paper.status, 'archived')

    def test_public_list_returns_published_only(self):
        """public 接口只返回已发布的试卷"""
        # 发布一份试卷
        self.own_paper.status = 'published'
        self.own_paper.save()

        response = self.client.get(self.public_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['id'], self.own_paper.pk)

    def test_stats_returns_published_paper_only(self):
        """stats 接口只接受已发布的试卷"""
        # 未发布时访问 stats
        self.client.force_authenticate(user=self.user)
        response = self.client.get(self.stats_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        # 发布后访问
        self.own_paper.status = 'published'
        self.own_paper.save()
        response = self.client.get(self.stats_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('paper_id', response.data)
        self.assertEqual(response.data['title'], self.own_paper.title)

    def test_delete_own_paper(self):
        """用户可以删除自己的试卷"""
        self.client.force_authenticate(user=self.user)
        response = self.client.delete(self.detail_url)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(ExamPaper.objects.filter(pk=self.own_paper.pk).exists())

    def test_delete_other_paper_denied(self):
        """用户不能删除他人试卷"""
        self.client.force_authenticate(user=self.user)
        response = self.client.delete(self.other_detail_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

class ExamPaperTenantTest(APITestCase):
    """测试多租户隔离逻辑"""
    def setUp(self):
        from apps.tenants.models import Tenant
        # 创建两个不同的租户
        self.tenant_a = Tenant.objects.create(name="Tenant A", code="tenant_a")
        self.tenant_b = Tenant.objects.create(name="Tenant B", code="tenant_b")

        # 创建各租户下的用户
        self.user_a = User.objects.create_user(
            username='user_a',
            email='a@example.com',
            password='pass12345',
            tenant=self.tenant_a
        )
        self.user_b = User.objects.create_user(
            username='user_b',
            email='b@example.com',
            password='pass12345',
            tenant=self.tenant_b
        )

        # 创建跨租户的管理员（假设管理员属于系统层，可访问所有租户）
        self.superuser = User.objects.create_superuser(
            username='admin',
            email='admin@example.com',
            password='admin123'
        )

        # 各租户创建科目
        self.subject_a = Subject.objects.create(
            name='数学_A',
            code='MATH_A',
            tenant=self.tenant_a
        )
        self.subject_b = Subject.objects.create(
            name='数学_B',
            code='MATH_B',
            tenant=self.tenant_b
        )

        # 各租户创建试卷
        self.paper_a = ExamPaper.objects.create(
            title='Tenant A 的试卷',
            creator=self.user_a,
            subject=self.subject_a,
            tenant=self.tenant_a,
            status='draft'
        )
        self.paper_b = ExamPaper.objects.create(
            title='Tenant B 的试卷',
            creator=self.user_b,
            subject=self.subject_b,
            tenant=self.tenant_b,
            status='draft'
        )
        # URL 别名
        self.list_url = reverse('exam-paper-list')
        self.detail_a_url = reverse('exam-paper-detail', kwargs={'pk': self.paper_a.pk})
        self.detail_b_url = reverse('exam-paper-detail', kwargs={'pk': self.paper_b.pk})

    def test_user_can_only_see_own_tenant_papers(self):
        """用户只能看到自己租户内的试卷"""
        self.client.force_authenticate(user=self.user_a)

        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = response.data['results']

        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]['id'], self.paper_a.id)
        # 确保看不到租户 B 的试卷
        self.assertNotIn(self.paper_b.id, [item['id'] for item in results])

    def test_user_cannot_access_other_tenant_objects(self):
        """用户无法查看或操作其他租户的试卷"""
        self.client.force_authenticate(user=self.user_a)

        # 尝试获取租户 B 的试卷详情
        response = self.client.get(self.detail_b_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        # 尝试更新租户 B 的试卷
        response = self.client.patch(self.detail_b_url, {'title': 'Hacked'})
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        # 尝试删除租户 B 的试卷
        response = self.client.delete(self.detail_b_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_tenant_is_set_on_create(self):
        """创建试卷时，tenant 应自动设置为当前用户的租户"""
        self.client.force_authenticate(user=self.user_a)

        data = {
            'title': '新试卷',
            'subject': self.subject_a.pk,
            'difficulty': 3
        }
        response = self.client.post(self.list_url, data)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        print(response.data)

        paper_id = response.data['id']
        created_paper = ExamPaper.objects.get(id=paper_id)
        self.assertEqual(created_paper.tenant, self.user_a.tenant)

    def test_admin_can_access_all_tenants_if_logic_allows(self):
        """超级管理员是否能跨租户访问（取决于 TenantViewSetMixin 实现）"""
        self.client.force_authenticate(user=self.superuser)

        # 假设 TenantViewSetMixin 不限制 superuser
        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # 检查是否能看到两个租户的试卷
        results = response.data['results']
        paper_ids = [item['id'] for item in results]
        self.assertIn(self.paper_a.id, paper_ids)
        self.assertIn(self.paper_b.id, paper_ids)

    def test_filter_by_tenant_in_queryset(self):
        """验证 get_queryset 是否正确应用 tenant 过滤"""
        from apps.exam_papers.views import ExamPaperViewSet

        request = type('Request', (), {'user': self.user_a})()
        view = ExamPaperViewSet()
        view.request = request

        queryset = view.get_queryset()
        papers = list(queryset.values_list('id', flat=True))

        self.assertIn(self.paper_a.id, papers)
        self.assertNotIn(self.paper_b.id, papers)

    def test_cannot_create_paper_without_tenant_context(self):
        """如果用户没有 tenant，创建应失败或抛出异常（边界情况）"""
        # 创建一个无 tenant 的用户（如系统用户）
        orphan_user = User.objects.create_user(
            username='orphan',
            email='orphan@example.com',
            password='pass12345',
            tenant=None
        )
        self.client.force_authenticate(user=orphan_user)

        data = {
            'title': '无租户试卷',
            'subject': self.subject_a.pk,
            'difficulty': 3
        }
        print(self.list_url)
        response = self.client.post(self.list_url, data)
        # 根据业务需求，可以是 400 或 403
        print(response.status_code)
        self.assertIn(response.status_code, [status.HTTP_400_BAD_REQUEST, status.HTTP_403_FORBIDDEN])